06 Spring 注解驱动开发之Web开发

在 Servlet3.0 之前,使用web的三大组件:servlet、filter、listener都需要在web.xml中进行注册配置。

在 Servlet3.0 标准发布之后,提供了注解的支持,异步处理的支持和可插拔的插件的支持。

Tomcat7.0以上的版本才支持Servlet3.0标准。

1 Servlet3.0中ServletContainerInitializer

Servlet3.0标准中引入了一个新的内容:shared librariesruntimes pluggability

Servlet 容器启动时会扫描当前应用里面每一个jar包的ServletContainerInitializer的实现类。 并且ServletContainerInitializer实现类,必须绑定在META-INF/services/javax.servlet.ServletContainerInitializer。文件的内容就是ServletContainerInitializer实现类的全类名。

简单说:容器在启动应用的时候,会扫描当前应用每一个jar包里面META-INF/services/javax.servlet.ServletContainerInitializer文件中指定的实现类,启动并运行这个实现类中的方法onStartup

ServletContainerInitializer的实现类是可以使用注解@HandlersTypes注解,作用是指定需要处理类型,容器启动的时候 将@HandlersTypes注解中指定的类型下的子类(实现类和子接口等)传递过来。

onStartup方法有两个参数:

  • Set<Class<>> set:需要处理的类型。是@HandlersTypes注解指定的类型的子类或者子接口。
  • ServletContext servletContext:代表当前web应用的ServletContext对象。(一个Web应用对应一个ServletContext对象)。可以使用它来注册三大组件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@HandlesTypes(value = {PersonService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {

/**
*
* @param set 需要处理的类型。是@HandlersTypes注解指定的类型的子类或者子接口。
* @param servletContext 代表当前web应用的ServletContext对象。(一个Web应用对应一个ServletContext对象)
* @throws ServletException
*/
@Override
public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {

}
}

2 整合Spring MVC

以前用xml的配置方式,需要在web.xml中先配置ContextLoaderListener监听器,来加载Spring的父容器。然后配置DispatcherServlet来配置Spring MVC前端控制器,加载子容器。

2.1 整合分析

javax.servlet.ServletContainerInitializer

我们可以查看spring-web-4.x.x.RELEASE包中META-INF/services/javax.servlet.ServletContainerInitializer文件里的内容:

1
org.springframework.web.SpringServletContainerInitializer

这就意味着在web容器启动的时候,会加载SpringServletContainerInitializer类:

该实现类上标注了@HandlesTypes({WebApplicationInitializer.class})。则Spring应用一启动,会加载WebApplicationInitializer接口下的子类和子接口。并且为不是接口和抽象类的WebApplicationInitializer组件创建对象。WebApplicationInitializer创建对象

WebApplicationInitializer接口有三个抽象实现:

AbstractContextLoaderInitializer:第一层抽象类,创建RootApplicationContext根容器。

AbstractDispatcherServletInitializer:第二层抽象类,创建一个web的容器。并创建一个DispatcherServlet,将创建的DispatcherServlet添加到ServletContext中。

AbstractAnnotationConfigDispatcherServletInitializer:第三层抽象类。注解方式创建根容器、创建DispatcherServlet

【总结】:以注解方式来启动springmvc,我们自己的配置类继承AbstractAnnotationConfigDispatcherServletInitializer,并实现抽象方法,指定DispatcherServlet的配置信息。

附 spring官方文档中推荐的配置方式:

spring官方配置方式

以父子容器的形式配置,web容器用来扫描Controller,视图解析器,映射等等web相关功能组件。根容器用来扫描业务逻辑组件、数据源、事务等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}

@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { App1Config.class };
}

@Override
protected String[] getServletMappings() {
return new String[] { "/app1/*" };
}
}

2.2 整合

一、根容器配置类:

1
2
3
4
5
6
// Spring的根容器不扫描标注了@Controller注解的类。
@ComponentScan(value = "com.enhao.spring.mvc.annotation", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
})
public class RootConfig {
}

二、子容器配置类:

1
2
3
4
5
6
// spring mvc子容器只扫描标注了@Controller注解的类,警用默认的过滤规则。
@ComponentScan(value = "com.enhao.spring.mvc.annotation", includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
},useDefaultFilters = false)
public class WebConfig {
}

三、创建自定义初始化类,继承AbstractAnnotationConfigDispatcherServletInitializer抽象类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// web容器启动的时候创建对象,调用方法来初始化容器以及前端控制器
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 获取根容器的配置类:类似于读取Spring的配置文件(创建出父容器)
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}

/**
* 获取web容器的配置类:类似于读取Spring MVC配置文件(创建出子容器)
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}

/**
* 获取DispatcherServlet的映射信息
* @return
*/
@Override
protected String[] getServletMappings() {
// "/":拦截所有请求,包含静态资源(xx.js, xx.png),但不包括*.jsp文件。
// "/*":拦截所有请求,包含*.jsp
return new String[]{"/"};
}
}

2.3 定制SpringMVC的配置

一、使用@EnableWebMvc注解,开启SpringMVC定制配置。相当于<mvc:annotation-driven />

二、配置组件:视图解析器、视图映射、静态资源映射、拦截器等等。

配置类实现WebMvcConfigurer接口,就可以配置以上提到的内容。

但实现WebMvcConfigurer接口里的方法太多了,所以我们通常继承WebMvcConfigurerAdapter抽象类,它是WebMvcConfigurer的一个空实现。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@ComponentScan(value = "com.enhao.spring.mvc.annotation", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
})
@EnableWebMvc
public class RootConfig extends WebMvcConfigurerAdapter {

// 配置视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// 默认页面都从/WEB-INF/xxx.jsp
// registry.jsp();
registry.jsp("/WEB-INF/views", ".jsp");
}

// 静态资源访问:相当于:<mvc:default-servlet-handler/>
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}

}

3 异步请求

Spring MVC的异步处理是基于Servlet3.0的异步处理。

一、方式一:控制器返回Callable

  1. 控制器返回Callable
  2. SpringMVC就会异步的将Callable提交到TaskExecutor使用一个隔离的线程进行执行。
  3. DispatcherServlet和所有的Filter退出web容器的线程,但response保持打开状态。
  4. Callable返回结果,SpringMVC将请求重新派发给容器,恢复之前的请求。
  5. 根据DispatcherServlet返回的结果,SpringMVC继续进行视图渲染流程等(收请求-视图渲染)。
1
2
3
4
5
6
7
8
9
10
11
12
13
@ResponseBody
@RequestMapping("/async01")
public Callable<String> async01() {

Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
return "async01";
}
};

return callable;
}

二、方式二:返回DeferredResult

一旦启用了异步请求处理功能 ,控制器就可以将返回值包装在DeferredResult,控制器可以从不同的线程异步产生返回值。优点就是可以实现两个完全不相干的线程间的通信。

以创建订单为例,客户端发起请求,应用A接受到这个请求,但应用A并不处理这个请求,通过消息中间件将这个请求交给应用B处理。

首先接受到请求后,创建一个DeferredResult对象,将这个对象保存起来,并将这个对象返回,这个请求就在等待中。当另外一个线程,例如消息中间件,调用了这个对象的deferredResult.setResult()方法。请求就会得到响应。

-------------本文结束感谢您的阅读-------------
0%